// #define _GNU_SOURCE
#include <string.h>
#include <cassert> 
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include "HttpRequest.h"
#include "Log.h"
#include "TcpConnection.h"

// 将字符转换为整形数
int HttpRequest::hexToDec(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

HttpRequest::HttpRequest()
{
    reset();
}

HttpRequest::~HttpRequest()
{
}

void HttpRequest::reset()
{
    m_curState = PrecessState::ParseReqLine;
    m_method = std::string();
    m_url = std::string();
    m_version = std::string();
    m_reqHeaders.clear();
}

void HttpRequest::addHeader(const std::string key, const std::string value)
{
    if(key.empty() || value.empty())
    {
        return;
    }
    m_reqHeaders.insert(std::make_pair(key, value));
}

std::string HttpRequest::getHeader(const std::string key)
{
    auto item = m_reqHeaders.find(key);
    if(item == m_reqHeaders.end())
    {
        return std::string();
    }
    return item->second;
}

bool HttpRequest::parseRequestLine(Buffer *readBuff)
{
     // 读出请求行
    char *end = readBuff->findCRLF();
    // 保存字符床起始地址
    const char *start = readBuff->data();
    // 保存字符床结束地址
    int32_t lineSize = end - start;

    if (lineSize)
    {
        // get /xxx/xx.txt http/1.1
        auto setMethodFunc = std::bind(&HttpRequest::setMethod, this, std::placeholders::_1);
        auto setUrlFunc = std::bind(&HttpRequest::setUrl, this, std::placeholders::_1);
        auto versionFunc = [this](std::string version){ return setVersion(version);};

        start = splitRequestLine(start, end, " ", setMethodFunc);
        start = splitRequestLine(start, end, " ", setUrlFunc);
        start = splitRequestLine(start, end, NULL, versionFunc);
        // 为解析请求头做准备
        readBuff->readPosIncrease(lineSize + 2);
        // 修改状态
        setCurState(PrecessState::ParseReqHeaders);
        return true;
    }
    return false;
}

bool HttpRequest::parseRequestHeader(Buffer *readBuff)
{
   char *end = readBuff->findCRLF();
    if (end != NULL)
    {   
        char *start = readBuff->data();
        int32_t lineSize = end - start;
        // 基于：搜索字符串
        char *middle = (char *)memmem(start, lineSize, ": ", 2);
        if (middle != nullptr)
        {
            int keyLen = middle - start;
            int valueLen = end - middle - 2;
            if(keyLen >0 && valueLen > 0 )
            {
                std::string key(start, keyLen);
                std::string value(middle + 2, valueLen);
                addHeader(key, value);
            }
            // 移动读数据的位置
            readBuff->readPosIncrease(lineSize + 2);
        }
        else
        {
            // 请求头被解析完了,跳过空行
            readBuff->readPosIncrease(2);
            // 修改解析状态
            // 忽略post请求 按照get请求处理
            setCurState(PrecessState::ParseReqDone);
        }
        return true;
    }
    return false;
}

bool HttpRequest::parseRequest(Buffer *readBuff, HttpResponse *response, Buffer *sendBuff, int32_t socket)
{
    printf("开始解析消息  cfd = %d\n", socket);
    bool flag = true;
    while (m_curState != PrecessState::ParseReqDone)
    {
        switch (m_curState)
        {
        case PrecessState::ParseReqLine:
            flag = parseRequestLine(readBuff);
            break;
        case PrecessState::ParseReqHeaders:
            flag = parseRequestHeader(readBuff);
            break;
        case PrecessState::ParseReqBody:
            break;
        default:
            break;
        }
        if (!flag)
        {
            return flag;
        }
        // 判断是否解析完毕了， 如果完毕了，需要准备回复的数据
        if (m_curState == PrecessState::ParseReqDone)
        {
            // 1.根据解析出的原始数据，对客户端的请求做出处理
            processRequest(response);
            printf("解析完毕消息，开始发送 cfd = %d\n", socket);
            // 2.组织响应数据并发送客户端
            response->prepareMsg(sendBuff, socket);
        }
    }
    m_curState = PrecessState::ParseReqLine; // 状态还原，保证还能继续处理第二条以及以后的请求
    return flag;
}

bool HttpRequest::processRequest(HttpResponse *response)
{
    if (strcasecmp(m_method.data(), "get") != 0)
    {
        return -1;
    }
    m_url = decodeMsg(m_url);
    // 处理客户端请求的静态资源（目录或者文件）
    const char *file = NULL;
    if (strcmp(m_url.data(), "/") == 0)
    {
        file = "./";
    }
    else
    {
        file = m_url.data() + 1;
    }
    struct stat st;
    int ret = stat(file, &st);
    if (ret == -1)
    {
        printf("file not exist\n");
        // 文件不存在 -- 回复404
        response->setFileName("404.html");
        response->setStatusCode(StatusCode::NotFound);
        // 响应头
        response->addHeader("Content-type", getFileType(".html"));
        response->sendDataFunc = sendFile;
        return true;
    }
    response->setFileName(file);
    response->setStatusCode(StatusCode::OK);
    // 判断文件类型
    if (S_ISDIR(st.st_mode))
    {
        // 响应头
        response->addHeader("Content-type", getFileType(".html"));
        response->sendDataFunc = sendDir;
    }
    else
    {
        // 把文件的内容发送给客户端
        // 响应头
        response->addHeader("Content-type", getFileType(file));
        response->addHeader("Content-length", std::to_string(st.st_size));
        response->sendDataFunc = sendFile;
    }
    return true;
}

// 解码
// to 存储解码之后的数据, 传出参数, from被解码的数据, 传入参数
std::string HttpRequest::decodeMsg(std::string msg)
{
    std::string str = std::string();
    const char *from = msg.data();
    for (; *from != '\0'; ++from)
    {
        // isxdigit -> 判断字符是不是16进制格式, 取值在 0-f
        // Linux%E5%86%85%E6%A0%B8.jpg
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
        {
            // 将16进制的数 -> 十进制 将这个数值赋值给了字符 int -> char
            // B2 == 178
            // 将3个字符, 变成了一个字符, 这个字符就是原始数据
            str.append(1,hexToDec(from[1]) * 16 + hexToDec(from[2]));
            // 跳过 from[1] 和 from[2] 因此在当前循环中已经处理过了
            from += 2;
        }
        else
        {
            // 字符拷贝, 赋值
            str.append(1, *from);
        }
    }
    str.append(1, '\0');
    return str;
}

const std::string HttpRequest::getFileType(const std::string fileName)
{
    const char *dot = strrchr(fileName.data(), '.');

    if (dot == NULL)
        return "text/plain; charset=utf-8"; // 纯文本
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp(dot, ".wav") == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";

    return "text/plain; charset=utf-8";
}

void HttpRequest::sendFile(const std::string fileName, Buffer *sendBuff, int32_t cfd)
{
    int32_t fd = open(fileName.data(), O_RDONLY);
    if (fd < 0)
    {
        perror("open error");
    }
    assert(fd > 0);
#if 1
    char buff[1024] = {0};
    int32_t len = 0;
    while ((len = read(fd, buff, sizeof(buff))) > 0)
    {
        sendBuff->appendData(buff, len);
        memset(buff, 0, sizeof(buff));
#ifndef MSG_SEND_AUTO
        sendBuff->sendData(cfd);
#endif
    }
    if (len == -1)
    {
        close(fd);
        perror("read error");
    }
    printf("发送cfd = %d fileName = %s 到sendbuff\n", cfd, fileName.data());
#else
    int64_t size = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET); // 重要！让文件指针回到开头
    off_t offset = 0;
    int64_t remaining = size;

    while (remaining > 0)
    {
        ssize_t sent = sendfile(cfd, fd, &offset, remaining);
        if (sent == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 等待 socket 可写再继续发送
                printf("无数据\n");
                continue;
            }
            perror("sendfile error");
            break;
        }
        remaining -= sent;
    }

#endif
    close(fd);
    return;
}

void HttpRequest::sendDir(const std::string dirName, Buffer *sendBuff, int32_t cfd)
{
    char buff[4096] = {0};
    snprintf(buff, sizeof(buff), "<html><head><title>%s</title></head><body><table>", dirName.data());
    struct dirent **nameList = NULL;
    int num = scandir(dirName.data(), &nameList, NULL, alphasort);
    for (int i = 0; i < num; ++i)
    {
        // 取出文件名
        char *name = nameList[i]->d_name;
        struct stat st;
        char subPath[1024] = {0};
        snprintf(subPath, sizeof(subPath), "%s/%s", dirName.data(), name);
        stat(subPath, &st);
        if (S_ISDIR(st.st_mode))
        {
            // a标签 <a href="">name</a>
            snprintf(buff + strlen(buff), sizeof(buff), "<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
                     name, name, st.st_size);
        }
        else
        {
            snprintf(buff + strlen(buff), sizeof(buff), "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
                     name, name, st.st_size);
        }
        sendBuff->appendString(buff);
        memset(buff, 0, sizeof(buff));
        free(nameList[i]);
    }
    snprintf(buff, strlen(buff), "</table></body></html>");
    sendBuff->appendString(buff);
    printf("直接发送目录   cfd = %d dirName = %s\n", cfd, dirName.data());
    sendBuff->sendData(cfd);
    free(nameList);
    return;
}

const char *HttpRequest::splitRequestLine(const char *start, const char *end, const char *sub, std::function<void(std::string)> callback)
{
     const char *space = end;
    if (sub != NULL)
    {
        space = (const char *)memmem(start, end - start, sub, strlen(sub));
        assert(space != NULL);
    }
    int32_t length = space - start;
    callback(std::string(start, length));
    return space + 1;
}
